﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Zhy.ModbusLib
{
    public abstract class ModbusSocket : ModbusMaster
    {
        /// <summary>
        /// IP地址
        /// </summary>
        public string IP { get; set; }
        /// <summary>
        /// 端口号
        /// </summary>
        public int Port { get; set; }
        /// <summary>
        /// 超时时间
        /// </summary>
        public int ReadTimeout { get; set; } = 2000;
        /// <summary>
        /// 设置缓存大小
        /// </summary>
        public int ReadBufferSize { get; set; } = 4096;
        /// <summary>
        /// TCP连接对象
        /// </summary>
        private Socket _socket;
        /// <summary>
        /// 用于系统线程的取消令牌
        /// </summary>
        private CancellationTokenSource cts = new CancellationTokenSource();

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="protocolType">通讯类型 TCP或UDP</param>
        protected ModbusSocket(ProtocolType protocolType)
        {
            if (protocolType == ProtocolType.Tcp)
            {
                _socket = new Socket(SocketType.Stream, protocolType); 
            }
            else if (protocolType == ProtocolType.Udp)
            {
                _socket = new Socket(SocketType.Dgram, protocolType);
            }
        }

        /// <summary>
        /// 连接
        /// </summary>
        public override void Connect()
        {
            _socket.ReceiveTimeout = ReadTimeout; // 设置超时时间
            _socket.ReceiveBufferSize = ReadBufferSize; // 设置缓存
            _socket.Connect(IP, Port); // 连接

            // 这里可以启动一个后台线程进行队列数据的获取和请求
            Task.Run(() =>
            {
                while (!cts.IsCancellationRequested) // 如果没有被销毁请求就继续执行
                {
                    // 判断串口已连接-判断队列有任务
                    if (_socket == null || asyncModels.Count == 0) continue;

                    var model = asyncModels.First(); // 获取队列任务

                    Exception _ex = null; // 异常信息
                    byte[] datas = null; // 处理后的返回报文
                    try
                    {
                        // 输出要发送的报文
                        Console.WriteLine(string.Join("-", model.ReqBytes.Select(b => b.ToString("X2"))));
                        // 发送和接收报文数据
                        byte[] resp = this.SendAndReceive(model.ReqBytes, model.RespLen);

                        // 执行校验逻辑，返回的应该是数据部分字节，同时中间可能还有异常
                        // 调用RTU的Check方法
                        // 调用ASCII的Check方法
                        datas = this.Check(resp);
                    }
                    catch (Exception ex)
                    {
                        // 在发送、接收、检测报文时可能会发生异常，需要将异常信息返回。
                        _ex = ex;
                    }
                    finally
                    {
                        // 执行完成时，将结果通过委托方法传出
                        model.Completed?.Invoke(datas, model.Handler, _ex);
                        // 根据请求ID在队列中移除已执行完成的任务
                        asyncModels.RemoveAll(id => id.Handler == model.Handler);
                    }
                }
            });
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        public override void Disconnect()
        {
            _socket?.Shutdown(SocketShutdown.Both); // 禁用发送和接收
            _socket?.Close(); // 关闭连接
            _socket?.Dispose(); // 释放资源
            _socket = null; // 释放资源
        }

        /// <summary>
        /// 发送和接收报文数据方法
        /// </summary>
        /// <param name="bytes">请求报文</param>
        /// <param name="len">正常返回报文字节数-(-1代表长度无效)</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected override byte[] SendAndReceive(byte[] bytes, int len)
        {
            // 发送报文数据
            _socket.Send(bytes, 0, bytes.Length, SocketFlags.None);

            byte[] resp = new byte[6]; // 获取头部报文，可以获取到后续报文的字节数
            _socket.Receive(resp, 0, 6, SocketFlags.None); // 从指定的socket中读取指定的字节数

            // 判断TID是否一致
            if (resp[0] != bytes[0] || resp[1] != bytes[1])
            {
                // 清除缓冲区    后期此处可以根据项目实际情况优化优化
                _socket.Receive(new byte[_socket.ReceiveBufferSize]);  // 在后续没有数据时会出现异常
                throw new Exception("TransactionID不匹配，接收数据无效");
            }

            len = resp[4] * 256 + resp[5]; // 计算后续还有多少字节数
            resp = new byte[len]; // 实例化后续字节数
            int count = _socket.Receive(resp, 0, len, SocketFlags.None); // 从指定socket中读取后续字节数

            return resp; // 返回接收的报文
        }

        #region 对返回的报文进行校验
        /// <summary>
        /// 对返回的报文进行校验
        /// </summary>
        /// <param name="resp">返回的报文</param>
        /// <param name="len">正常报文接收长度</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected virtual byte[] Check(byte[] resp) { return null; }
        #endregion
    }
}
